package com.hspedu.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.hspedu.seckill.config.AccessLimit;
import com.hspedu.seckill.pojo.SeckillMessage;
import com.hspedu.seckill.pojo.SeckillOrder;
import com.hspedu.seckill.pojo.User;
import com.hspedu.seckill.rabbitmq.MQSenderMessage;
import com.hspedu.seckill.service.GoodsService;
import com.hspedu.seckill.service.OrderService;
import com.hspedu.seckill.service.SeckillOrderService;
import com.hspedu.seckill.vo.GoodsVo;
import com.hspedu.seckill.vo.RespBean;
import com.hspedu.seckill.vo.RespBeanEnum;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.common.Fonts;
import com.ramostear.captcha.support.CaptchaStyle;
import com.ramostear.captcha.support.CaptchaType;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {

    //装配GoodsService
    @Resource
    private GoodsService goodsService;

    //装配OrderService
    @Resource
    private OrderService orderService;

    //装配SeckillOrderService
    @Resource
    private SeckillOrderService seckillOrderService;


    //装配RedisTemplate
    @Resource
    private RedisTemplate redisTemplate;

    //定义map-记录秒杀商品是否还有库存
    private Map<Long, Boolean> entryStockMap = new HashMap<>();

    //装配MQSenderMessage消息发送者
    @Resource
    private MQSenderMessage mqSenderMessage;

    @Resource
    private RedisScript<Long> redisScript;


    //响应处理用户秒杀的请求
    //我们先完成一个v1.0的版本，后面在高并发的情况下，还要做优化
    // @RequestMapping("/doSeckill")
    // public String doSeckill(Long goodsId, Model model, User user) {
    //
    //     System.out.println("------秒杀v1.0------");
    //
    //     if (null == user) {//用户没有登录
    //         return "login";
    //     }
    //
    //     //将user放入model，给下一个模板页使用
    //     model.addAttribute("user", user);
    //
    //     //获取到GoodsVo
    //     GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
    //
    //     //判断库存
    //     if (goodsVo.getStockCount() < 1) {//库存不足
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //         return "seckillFail";
    //     }
    //
    //     //判断用户是否复购-判断当前购买用户的id和购买商品id是否已经在商品秒杀表存在
    //     SeckillOrder seckillOrder = seckillOrderService.getOne(new QueryWrapper<SeckillOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
    //
    //     if (null != seckillOrder) {
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.REPEAT_ERROR));
    //         return "seckillFail";
    //     }
    //
    //     Order order = orderService.seckill(user, goodsVo);
    //
    //     if (null == order){
    //         model.addAttribute("errmsg",RespBean.error(RespBeanEnum.SECKILL_ERROR));
    //         return "seckillFail";
    //     }
    //
    //     model.addAttribute("order", order);
    //     model.addAttribute("goods", goodsVo);
    //
    //     System.out.println("------秒杀v1.0------");
    //
    //     //进入到订单详情页
    //     return "orderDetail";
    // }


    //响应处理用户秒杀的请求
    //我们先完成一个v2.0的版本，后面在高并发的情况下，还要做优化
    //@RequestMapping("/doSeckill")
    //public String doSeckill(Long goodsId, Model model, User user) {
    //
    //    System.out.println("------秒杀v2.0------");
    //
    //    if (null == user) {//用户没有登录
    //        return "login";
    //    }
    //
    //    //将user放入model，给下一个模板页使用
    //    model.addAttribute("user", user);
    //
    //    //获取到GoodsVo
    //    GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
    //
    //    //判断库存
    //    if (goodsVo.getStockCount() < 1) {//库存不足
    //        model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //        return "seckillFail";
    //    }
    //
    //    //判断用户是否复购-直接到redis中，获取对应的秒杀订单，如果有，则说明已经抢购了
    //    SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsVo.getId());
    //
    //    if (null != seckillOrder) {//说明该用户已经抢购了该商品
    //        model.addAttribute("errmsg", RespBean.error(RespBeanEnum.REPEAT_ERROR));
    //        return "seckillFail";
    //    }
    //
    //    Order order = orderService.seckill(user, goodsVo);
    //
    //    if (null == order) {
    //        model.addAttribute("errmsg", RespBean.error(RespBeanEnum.SECKILL_ERROR));
    //        return "seckillFail";
    //    }
    //
    //    model.addAttribute("order", order);
    //    model.addAttribute("goods", goodsVo);
    //
    //    System.out.println("------秒杀v2.0------");
    //
    //    //进入到订单详情页
    //    return "orderDetail";
    //}

    //响应处理用户秒杀的请求
    //我们先完成一个v3.0的版本,Redis库存预减
    // @RequestMapping("/doSeckill")
    // public String doSeckill(Long goodsId, Model model, User user) {
    //
    //     System.out.println("------秒杀v3.0------");
    //
    //     if (null == user) {//用户没有登录
    //         return "login";
    //     }
    //
    //     //将user放入model，给下一个模板页使用
    //     model.addAttribute("user", user);
    //
    //     //获取到GoodsVo
    //     GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
    //
    //     //判断库存
    //     if (goodsVo.getStockCount() < 1) {//库存不足
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //         return "seckillFail";
    //     }
    //
    //
    //     //判断用户是否复购-直接到redis中，获取对应的秒杀订单，如果有，则说明已经抢购了
    //     SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
    //
    //     if (null != seckillOrder) {//说明该用户已经抢购了该商品
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.REPEAT_ERROR));
    //         return "seckillFail";
    //     }
    //
    //     //库存预减，如果在Redis中预减库存，发现描述商品已经没有了，就直接返回，从而减少去执行orderService.seckill这个方法的请求，防止线程堆积，优化秒杀，高并发
    //     //decrement这个方法是具有原子性的
    //     Long stockCount = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
    //
    //     if (stockCount < 0){
    //         //恢复库存为0
    //         redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //         return "seckillFail";
    //     }
    //
    //     Order order = orderService.seckill(user, goodsVo);
    //
    //     if (null == order) {
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.SECKILL_ERROR));
    //         return "seckillFail";
    //     }
    //
    //     model.addAttribute("order", order);
    //     model.addAttribute("goods", goodsVo);
    //
    //     System.out.println("------秒杀v3.0------");
    //
    //     //进入到订单详情页
    //     return "orderDetail";
    // }

    //响应处理用户秒杀的请求
    //我们先完成一个v4.0的版本,加入内存标记优化
    // @RequestMapping("/doSeckill")
    // public String doSeckill(Long goodsId, Model model, User user) {
    //
    //     System.out.println("------秒杀v4.0------");
    //
    //     if (null == user) {//用户没有登录
    //         return "login";
    //     }
    //
    //     //将user放入model，给下一个模板页使用
    //     model.addAttribute("user", user);
    //
    //     //获取到GoodsVo
    //     GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
    //
    //     //判断库存
    //     if (goodsVo.getStockCount() < 1) {//库存不足
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //         return "seckillFail";
    //     }
    //
    //
    //     //判断用户是否复购-直接到redis中，获取对应的秒杀订单，如果有，则说明已经抢购了
    //     SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
    //
    //     if (null != seckillOrder) {//说明该用户已经抢购了该商品
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.REPEAT_ERROR));
    //         return "seckillFail";
    //     }
    //
    //     //对entryStockMap进行判断[内存标记]，如果商品在entryStockMap已经标记为没有库存，则直接返回，无需进行redis预减操作
    //     if (entryStockMap.get(goodsId)){
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //         return "seckillFail";
    //     }
    //
    //     //库存预减，如果在Redis中预减库存，发现描述商品已经没有了，就直接返回，从而减少去执行orderService.seckill这个方法的请求，防止线程堆积，优化秒杀，高并发
    //     //decrement这个方法是具有原子性的
    //     Long stockCount = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
    //
    //     if (stockCount < 0){
    //         //说明当前秒杀的商品，已经没有库存
    //         entryStockMap.put(goodsId,true);
    //         //恢复库存为0
    //         redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
    //         return "seckillFail";
    //     }
    //
    //     Order order = orderService.seckill(user, goodsVo);
    //
    //     if (null == order) {
    //         model.addAttribute("errmsg", RespBean.error(RespBeanEnum.SECKILL_ERROR));
    //         return "seckillFail";
    //     }
    //
    //     model.addAttribute("order", order);
    //     model.addAttribute("goods", goodsVo);
    //
    //     System.out.println("------秒杀v4.0------");
    //
    //     //进入到订单详情页
    //     return "orderDetail";
    // }

    //响应处理用户秒杀的请求
    //我们先完成一个v5.0的版本,加入消息队列，实现秒杀的异步请求
    @RequestMapping("/doSeckill")
    public String doSeckill(Long goodsId, Model model, User user) {


        if (null == user) {//用户没有登录
            return "login";
        }

        //将user放入model，给下一个模板页使用
        model.addAttribute("user", user);
        model.addAttribute("goodsId", goodsId);

        //获取到GoodsVo
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);

        //判断库存
        if (goodsVo.getStockCount() < 1) {//库存不足
            model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
            return "seckillFail";
        }


        //判断用户是否复购-直接到redis中，获取对应的秒杀订单，如果有，则说明已经抢购了
        SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);

        if (null != seckillOrder) {//说明该用户已经抢购了该商品
            model.addAttribute("errmsg", RespBean.error(RespBeanEnum.REPEAT_ERROR));
            return "seckillFail";
        }

        //对entryStockMap进行判断[内存标记]，如果商品在entryStockMap已经标记为没有库存，则直接返回，无需进行redis预减操作
        if (entryStockMap.get(goodsId)) {
            model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
            return "seckillFail";
        }

        //库存预减，如果在Redis中预减库存，发现描述商品已经没有了，就直接返回，从而减少去执行orderService.seckill这个方法的请求，防止线程堆积，优化秒杀，高并发
        //decrement这个方法是具有原子性的
        //Long stockCount = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
        //
        //if (stockCount < 0) {
        //    //说明当前秒杀的商品，已经没有库存
        //    entryStockMap.put(goodsId, true);
        //    //恢复库存为0
        //    redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
        //    model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
        //    return "seckillFail";
        //}

        //------使用redis分布式锁------
        //1. 对于当前项目而言，使用redisTemplate.opsForValue().decrement() 就可以控制抢购，因为该方法具有原子性隔离性
        //2. 考虑到如果有比较多的操作，需要保证隔离性，也就是说，不是简单的-1，而是有多个操作
        //    这样就需要扩大隔离性的范围，部分操作还需要原子性，这里就需要使用到redis分布式锁

        //1 获取锁，setnx
        //得到一个uuid值，作为锁的值
        String uuid = UUID.randomUUID().toString();

        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);

        //2 获取锁成功、查询 num 的值
        if (lock){

            // 定义 lua 脚本
            //String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            //DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //redisScript.setScriptText(script);
            //redisScript.setResultType(Long.class);



            Long stockCount = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);

            if (stockCount < 0) {
                //说明当前秒杀的商品，已经没有库存
                entryStockMap.put(goodsId, true);
                //恢复库存为0
                redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);

                //释放锁,lua脚本
                redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);

                model.addAttribute("errmsg", RespBean.error(RespBeanEnum.ENTRY_STOCK));
                return "seckillFail";
            }

            //释放分布式锁
            redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);

        }else {
            //3 获取锁失败，返回信息(抢购失败，请重试)
            model.addAttribute("errmsg",RespBean.error(RespBeanEnum.SEC_KILL_RETRY));
            return "seckillFail";
        }



        //抢购，先消息队列发送秒杀请求，实现了秒杀异步请求
        //这里我们发送秒杀消息后，立即快速返回临时结构，比如"排队中..."
        //客户端通过轮询，获取到最终结构
        //创建SeckillMessage
        SeckillMessage message = new SeckillMessage(user, goodsId);
        mqSenderMessage.senderSeckillMessage(JSONUtil.toJsonStr(message));
        model.addAttribute("errmsg", "排队中...");

        return "seckillFail";

    }


    //响应处理用户秒杀的请求
    //我们先完成一个v6.0的版本,加入秒杀安全
    //@RequestMapping("/{path}/doSeckill")
    //@ResponseBody
    //public RespBean doSeckill(@PathVariable String path, Long goodsId, Model model, User user) {
    //
    //    if (null == user) {//用户没有登录
    //        return RespBean.error(RespBeanEnum.SESSION_ERROR);
    //    }
    //
    //    //这里我们增加一个判断逻辑，校验用户携带的path是否正确
    //    if (!orderService.checkPath(user, goodsId, path)) {
    //        return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
    //    }
    //
    //    //获取到GoodsVo
    //    GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
    //
    //    //判断库存
    //    if (goodsVo.getStockCount() < 1) {//库存不足
    //        return RespBean.error(RespBeanEnum.ENTRY_STOCK);
    //    }
    //
    //
    //    //判断用户是否复购-直接到redis中，获取对应的秒杀订单，如果有，则说明已经抢购了
    //    SeckillOrder seckillOrder = (SeckillOrder) redisTemplate.opsForValue().get("order:" + user.getId() + ":" + goodsId);
    //
    //    if (null != seckillOrder) {//说明该用户已经抢购了该商品
    //        return RespBean.error(RespBeanEnum.REPEAT_ERROR);
    //    }
    //
    //    //对entryStockMap进行判断[内存标记]，如果商品在entryStockMap已经标记为没有库存，则直接返回，无需进行redis预减操作
    //    if (entryStockMap.get(goodsId)) {
    //        return RespBean.error(RespBeanEnum.ENTRY_STOCK);
    //    }
    //
    //    //库存预减，如果在Redis中预减库存，发现描述商品已经没有了，就直接返回，从而减少去执行orderService.seckill这个方法的请求，防止线程堆积，优化秒杀，高并发
    //    //decrement这个方法是具有原子性的
    //    Long stockCount = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
    //
    //    if (stockCount < 0) {
    //        //说明当前秒杀的商品，已经没有库存
    //        entryStockMap.put(goodsId, true);
    //        //恢复库存为0
    //        redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
    //        return RespBean.error(RespBeanEnum.ENTRY_STOCK);
    //    }
    //
    //    //抢购，先消息队列发送秒杀请求，实现了秒杀异步请求
    //    //这里我们发送秒杀消息后，立即快速返回临时结构，比如"排队中..."
    //    //客户端通过轮询，获取到最终结构
    //    //创建SeckillMessage
    //    SeckillMessage message = new SeckillMessage(user, goodsId);
    //    mqSenderMessage.senderSeckillMessage(JSONUtil.toJsonStr(message));
    //
    //    return RespBean.error(RespBeanEnum.SEC_KILL_WAIT);
    //
    //}


    //方法:获取秒杀路径
    @RequestMapping("/path")
    @ResponseBody
    /**
     * 1. 使用自定义注解的方式对用户的限流防刷，通用性和灵活性提高
     * 2. second = 5, maxCount = 5 说明在5秒内可以访问的最大次数是5次
     * 3. needLogin = true 表示用户是否需要登录，默认true
     */
    @AccessLimit(second = 5, maxCount = 5, needLogin = true)
    public RespBean getPath(User user, Long goodsId, String captcha, HttpServletRequest request) {
        if (null == user || goodsId < 0 || !StringUtils.hasText(captcha)) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }

        //增加业务逻辑：加入Redis计数器，完成对用户的限流防刷
        //比如在5秒内访问的次数超过5次，我们就认为是在刷接口
        //这里先把代码写在方法中，后面我们使用注解提高使用的通用性

        // String uri = request.getRequestURI();
        // String key = uri + ":" + user.getId();
        //
        // Integer count = (Integer) redisTemplate.opsForValue().get(uri + ":" + user.getId());
        // if (null == count) {//说明还没有这个key，就初始化值为1，过期时间为5秒
        //     redisTemplate.opsForValue().set(key, 1, 5, TimeUnit.SECONDS);
        // } else if (count < 5) {//说明正常访问
        //     redisTemplate.opsForValue().increment(key);
        // } else {//说明用户在刷接口
        //     return RespBean.error(RespBeanEnum.ACCESS_LIMIT_REACHED);
        // }

        //增加一个业务逻辑，验证用户输入的验证码是否正确
        boolean check = orderService.checkCaptcha(user, goodsId, captcha);
        if (!check) {//如果验证码错误
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }

        String path = orderService.createPath(user, goodsId);


        return RespBean.success(path);
    }


    //生成验证码-happyCaptcha
    @RequestMapping("/captcha")
    public void happyCaptcha(HttpServletRequest request, HttpServletResponse response, User user, Long goodsId) {
        //生成验证码并输出
        //注意，该验证码，默认保存在session中，key是happy-captcha
        HappyCaptcha.require(request, response)
                .style(CaptchaStyle.ANIM)             //设置展现样式为动画
                .type(CaptchaType.NUMBER)            //设置验证码内容为汉字
                .length(6)                            //设置字符长度为6
                .width(220)                           //设置动画宽度为220
                .height(80)                           //设置动画高度为80
                .font(Fonts.getInstance().zhFont())   //设置汉字的字体
                .build().finish();                //生成并输出验证码

        //把验证码的值，保存到Redis
        //key:  captcha:userId:goodsId
        redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, (String) request.getSession().getAttribute("happy-captcha"), 60, TimeUnit.SECONDS);
    }


    //该方法是在类的所有属性初始化后，自动执行的
    //这里我们就可以将所有秒杀商品的库存量，加载到Redis
    @Override
    public void afterPropertiesSet() throws Exception {
        //查询所有的秒杀商品
        List<GoodsVo> list = goodsService.findGoodsVo();
        //先判断是否为空
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        //遍历List,然后将秒杀商品的库存量，放入到Redis
        //秒杀商品的库存量对应key : seckillGoods:商品id
        list.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());
            //初始化entryStockMap
            //如果goodsId：false 表示有库存
            //如果goodsId：true 表示没有库存
            entryStockMap.put(goodsVo.getId(), false);
        });

    }

    //响应前端轮询秒杀结果
    @RequestMapping("/getSecKillResult")
    @ResponseBody
    public RespBean getSecKillResult(User user, Long goodsId) {
        //到redis查询秒杀结果
        Integer secKillResult = (Integer) redisTemplate.opsForValue().get("seckill:" + user.getId() + ":" + goodsId);
        System.out.println("secKillResult=" + secKillResult);

        //如果没有查到该用户对应的秒杀商品结果就将结果设置为0
        if (null == secKillResult) {
            secKillResult = 0;
        }
        //返回
        return RespBean.success(secKillResult);
    }


}
